﻿/// <reference path="typings/jquery/jquery.d.ts" />
/// <reference path="typings/bootstrap/bootstrap.d.ts" />
/// <reference path="typings/knockout/knockout.d.ts" />
/// <reference path="typings/signalr/signalr.d.ts" />

enum AttachmentType {
    PICTURE,
    VOICE,
    VIDEO,
    UNKNOWN
}

enum AdminLevel {
    USER,
    VOICE,
    ADMIN
}

interface User {
    Id: string;
    Nick: string;
    AdminState: AdminLevel;
}

interface ChannelModel {
    HashTag: string;
    Topic: string;
    Private: boolean;
    UserList: User[];
    Messages: MessageModel[];
}

interface MessageModel {
    Id: string;
    UserId: string;
    HashTag: string;
    Message: string;
    AttachmentId: string;
    TimeStamp: Date;
    SystemMessage: boolean;
}

interface AttachmentModel {
    Id: string;
    Type: AttachmentType;
    OriginalName: string;
    InternalName: string;
    URL: KnockoutObservable<string>;
}

declare var $parentContext;

class ChatViewModel {
    private State: KnockoutObservable<number> = ko.observable(0);

    // Register/Login variables
    private Nick: KnockoutObservable<string> = ko.observable("");
    private Email: KnockoutObservable<string> = ko.observable("");
    private Password: KnockoutObservable<string> = ko.observable("");
    private Password2: KnockoutObservable<string> = ko.observable("");

    private Loading: KnockoutObservable<boolean> = ko.observable(false);

    // Chat variables
    private Channels: KnockoutObservableArray<ChannelModel> = ko.observableArray([]);
    private CurrentChannel: KnockoutObservable<string> = ko.observable("");
    private CurrentUserList: KnockoutObservableArray<User> = ko.observableArray([]);
    private Message: KnockoutObservable<string> = ko.observable("");
    private CurrentMessages: KnockoutObservableArray<MessageModel> = ko.observableArray([]);
    private Attachments: Object = {};
    // The ID of the selected user or null if no user selected
    private SelectedUser: KnockoutObservable<string> = ko.observable("");

    private SignalrChat: ChatHubProxy = null;

    // Join template
    private JoinInput: KnockoutObservable<string> = ko.observable("").extend({ rateLimit: 500 });
    private JoinQueryResults: KnockoutObservableArray<string> = ko.observableArray([]);

    public constructor() {
        this.JoinInput.subscribe((newValue: string) => {
            $.ajax("/api/Channel/" + encodeURIComponent(newValue), this.getAjaxLoggedInOpts("GET")).done((Channels: ChannelModel[]) => {
                var schannels: string[] = [];
                for (var i = 0; i < Channels.length; i++) {
                    schannels.push(Channels[i].HashTag);
                }
                this.JoinQueryResults(schannels);
            });
        }, this);
        var token: string = this.getBearerToken();
        if (token) {
            this.InitChannels();
        }
    }

    private JoinChannel = (data: string, event: JQueryEventObject) => {
        $.ajax("/api/Chat/" + encodeURIComponent(data), this.getAjaxLoggedInOpts("PUT")).done((rdata: ChannelModel) => {
            if (this.Channels().length == 0) {
                // This is our first joined channel
                this.Channels.push(rdata);
                this.InitChat(rdata);
            } else {
                rdata.Messages = [];
                rdata.UserList = [];
                this.Channels.push(rdata);
                this.ChatState();
                this.ChangeChannel(rdata);
            }
            
        }).fail((error) => {
            console.error(error);
            alert(error.responseJSON.Message);
        });
    }

    private JoinHeseNyymit() {
        this.JoinChannel("#hesenyymit", null);
    }

    private LeaveChannel() {
        if (!confirm("Leave channel " + this.CurrentChannel() + "?"))
            return;

        $.ajax("/api/Chat/" + encodeURIComponent(this.CurrentChannel()), this.getAjaxLoggedInOpts("DELETE")).done((rdata: number) => {
            if (this.Channels().length == 1) {
                // This is our last joined channel
                this.DisconnectFromChat();
                this.State(2);
            } else {
                this.Channels.remove((item: ChannelModel) => { return item.HashTag == this.CurrentChannel() });
                this.ChangeChannel(this.Channels()[0]);
            }

        }).fail((error) => {
            console.error(error);
            alert(error.responseJSON.Message);
        });
    }

    private ChangeChannel = (data: ChannelModel, event: JQueryEventObject = null) => {
        this.CurrentChannel(data.HashTag);
        this.SelectedUser("");
        if (data.UserList.length > 0) {
            this.CurrentUserList(data.UserList);
            this.CurrentMessages(data.Messages);
            this.scrollToBottom();
            return;
        }
        this.GetChannelUsers(data.HashTag);
        this.GetChannelMessages(data.HashTag);
    }

    private CreateChannel() {
        var data: any = {
            HashTag: "#" + this.JoinInput(),
            Topic: "Change me!",
            Private: false
        }

        $.ajax("/api/Channel", this.getAjaxLoggedInOpts("POST", data)).done((rdata) => {
            data.Messages = [];
            data.UserList = [];
            if (this.Channels().length == 0) {
                this.Channels.push(data);
                this.InitChat(data);
            } else {
                this.Channels.push(data);
                this.CurrentChannel(data.HashTag);
                var luser: User = {
                    Id: "1",
                    Nick: this.Nick(),
                    AdminState: AdminLevel.ADMIN
                }
                this.CurrentUserList([]);
                this.CurrentUserList.push(luser);
                this.CurrentMessages([]);
                this.State(3);
            }
        }).fail((error) => {
            console.error(error);
            alert(error.responseJSON.Message);
        });
    }

    // Simple validation and the ajax call to register.
    private Register() {
        if (this.Nick().length < 1)
            return;

        if (this.Email().length < 1)
            return;

        if (this.Password().length < 1 || this.Password2().length < 1)
            return;

        if (this.Password() != this.Password2())
            return;

        var data: any = {
            Email: this.Email(),
            Nick: this.Nick(),
            Password: this.Password(),
            ConfirmPassword: this.Password2()
        }

        $.ajax("/api/Account/Register", this.getAjaxLoggedOutOpts("POST", data)).done((rdata) => {
            console.log(rdata);
            this.Login();
        }).fail((error) => {
            console.error(error);
            alert(error.responseJSON.error_description);
        });
    }

    private Login() {
        if (this.Nick().length < 1)
            return;

        if (this.Password().length < 1)
            return;

        if (this.Loading() == true)
            return;
        this.Loading(true);

        var data = "grant_type=password&username=" + this.Nick() + "&password=" + this.Password();
        var opts: JQueryAjaxSettings = {
            method: "POST",
            data: data,
            cache: false,
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        };

        $.ajax("/Token", opts).done((rdata) => {
            console.log(rdata);
            this.setBearerToken(rdata.access_token); //, userName: loginData.userName }
            this.InitChannels();
        }).fail((error) => {
            console.error(error);
            alert(error.responseJSON.error_description);
            this.Loading(false);
        });
    }

    // Gets the user's channels and starts the chat
    private InitChannels() {
        this.GetChannels().done((Channels: ChannelModel[]) => {
            if (Channels.length > 0) {
                for (var i = 0; i < Channels.length; i++) {
                    Channels[i].UserList = [];
                    Channels[i].Messages = [];
                }
                this.Channels(Channels);
                this.InitChat(Channels[0]);
            } else {
                this.State(2);
            }
        }).fail((error) => {
            this.setBearerToken(null);
        }).always(() => {
            this.Loading(false);
        });
    }

    // Initialize the chat with channel
    private InitChat(channel: ChannelModel) {
        this.CurrentChannel(channel.HashTag);
        var pr1: JQueryPromise<any> = this.GetChannelUsers(channel.HashTag);
        var pr2: JQueryPromise<any> = this.GetChannelMessages(channel.HashTag);
        $.when(pr1, pr2).done((result1: any[], result2: any[]) => {
            this.State(3);
            this.scrollToBottom();
            this.ConnectToChat();
        }).fail((error) => {
            console.error(error);
        });
    }

    // Initializes the SignalR protocol for chat
    private ConnectToChat() {
        this.SignalrChat = $.connection.chatHub;
        // This is ran every time message is received
        this.SignalrChat.client.receive = (data: MessageModel) => {
            data.TimeStamp = new Date(data.TimeStamp.toString());
            if (data.AttachmentId) {
                var attachment: AttachmentModel = <AttachmentModel> {
                    Id: data.AttachmentId,
                    URL: ko.observable("")
                }
                this.Attachments[data.AttachmentId] = attachment;
                this.GetAttachment(data.AttachmentId);
            }

            if (this.CurrentChannel() == data.HashTag) {
                this.CurrentMessages.push(data);
            } else {
                for (var i = 0; i < this.Channels().length; i++) {
                    if (data.HashTag != this.Channels()[i].HashTag)
                        continue;
                    this.Channels()[i].Messages.push(data);
                    break;
                }
            }
            
            if (data.SystemMessage == true) {
                this.GetChannelUsers(data.HashTag);
            }
            this.scrollToBottom();
        };
        this.createCookie("Bearer", this.getBearerToken(), 1);
        $.connection.hub.start().done(() => {
            console.log("Signalr started");
        });
    }

    private DisconnectFromChat() {
        $.connection.hub.stop();
    }

    private GetAttachment(attachmentId: string, scroll: boolean = false): JQueryPromise<any> {
        return $.ajax("/api/Attachment/" + encodeURIComponent(attachmentId), this.getAjaxLoggedInOpts("GET")).done((rdata: AttachmentModel) => {
            this.Attachments[attachmentId].InternalName = rdata.InternalName;
            this.Attachments[attachmentId].OriginalName = rdata.OriginalName;
            this.Attachments[attachmentId].Type = rdata.Type;
            this.Attachments[attachmentId].URL(rdata.InternalName);
            if (scroll)
                this.scrollToBottom();
        });
    }

    private SendAttachment() {
        var file = $("#attachment").prop("files")[0];
        if (file == void 0)
            return false;
        var formData = new FormData();
        formData.append("file-0", file);
        jQuery.ajax({
            url: "/api/Attachment/",
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            type: "POST",
            headers: {
                'Authorization': 'Bearer ' + this.getBearerToken()
            }
        }).done((rdata: string[]) => {
            (<HTMLFormElement>$("#InlineChatForm")[0]).reset();
            if (rdata.length == 0) {
                console.log("Attachment was ignored!");
                this.SendMessageEx();
                return;
            }
            console.log("AttachmentId: " + rdata[0]);
            this.SendMessageEx(rdata[0]);
        }).fail((error) => {
            console.error("Failed to send the attachment!");
        });
        return true;
    }

    private SendMessageEx(attachmentId: string = null) {
        var data: MessageModel = <MessageModel> {
            HashTag: this.CurrentChannel(),
            Message: this.Message(),
            AttachmentId: attachmentId
        }
        $.ajax("/api/Chat/", this.getAjaxLoggedInOpts("POST", data)).done((rdata) => {
            this.Message("");
        });
    }

    private SendMessage() {
        if (this.SendAttachment())
            return;

        this.SendMessageEx();
    }

    private CheckForEnter(data: any, event: JQueryEventObject) {
        if (event.keyCode == 13) {
            this.SendMessage();
        }
        return true;
    }

    private GetChannels(): JQueryXHR {
        return $.ajax("/api/UserList/MyChannels", this.getAjaxLoggedInOpts());
    }

    private SetUser = (selectedUser: User, event: JQueryEventObject) => {
        if (this.SelectedUser() == selectedUser.Id) {
            this.SelectedUser("");
            return;
        }
        this.SelectedUser(selectedUser.Id);
    }

    private GetChannelUsers(channelName: string): JQueryPromise<any> {
        return $.ajax("/api/UserList/Users/" + encodeURIComponent(channelName), this.getAjaxLoggedInOpts("GET")).done((users: User[]) => {
            for (var i = 0; i < this.Channels().length; i++) {
                if (this.Channels()[i].HashTag == channelName) {
                    this.Channels()[i].UserList = users;
                    break;
                }
            }
            if (this.CurrentChannel() == channelName) {
                this.CurrentUserList(users);
            }
        });
    }

    private GetChannelMessages(channelName: string): JQueryPromise<any> {
        return $.ajax("/api/Chat/" + encodeURIComponent(channelName), this.getAjaxLoggedInOpts()).done((messages: MessageModel[]) => {
            for (var i = 0; i < messages.length; i++) {
                messages[i].TimeStamp = new Date(messages[i].TimeStamp.toString());
                if (messages[i].AttachmentId) {
                    var attachment: AttachmentModel = <AttachmentModel> {
                        Id: messages[i].AttachmentId,
                        URL: ko.observable("")
                    }
                    this.Attachments[messages[i].AttachmentId] = attachment;
                    this.GetAttachment(messages[i].AttachmentId, true);
                }
            }
            for (var i = 0; i < this.Channels().length; i++) {
                if (this.Channels()[i].HashTag == channelName) {
                    this.Channels()[i].Messages = messages;
                    break;
                }
            }
            if (this.CurrentChannel() == channelName) {
                this.CurrentMessages(messages);
            }
        });
    }

    private GetNick(userId: string): string {
        for (var i = 0; i < this.CurrentUserList().length; i++) {
            if (this.CurrentUserList()[i].Id == userId)
                return this.CurrentUserList()[i].Nick;
        }
        return "?";
    }

    private GetTimeString(time: Date): string {
        return time.toUTCString();
    }
    
    private RegisterState() {
        this.State(1);
        return true;
    }

    private LoginState() {
        this.State(0);
        return true;
    }

    private JoinChannelState() {
        this.State(4);
        return true;
    }

    private ChatState() {
        this.State(3);
        return true;
    }

    private getAjaxLoggedOutOpts(method: string = "GET", data: Object = null): JQueryAjaxSettings {
        if (data)
            data = JSON.stringify(data);
        var opts: JQueryAjaxSettings = {
            method: method,
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data: data,
            cache: false
        };
        return opts;
    }

    private getAjaxLoggedInOpts(method: string = "GET", data: Object = null, json: boolean = true): JQueryAjaxSettings {
        if (data && json)
            data = JSON.stringify(data);
        var opts: JQueryAjaxSettings = {
            method: method,
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data: data,
            cache: false,
            headers: {
                'Authorization': 'Bearer ' + this.getBearerToken()
            }
        };
        return opts;
    }

    private getBearerToken(): string {
        return sessionStorage.getItem("bearertoken");
    }

    private setBearerToken(token: string) {
        sessionStorage.setItem("bearertoken", token);
    }

    private createCookie(name: string, value: string, days: number) {
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            var expires = "; expires=" + date.toUTCString();
        }
        else var expires = "";
        document.cookie = name + "=" + value + expires + "; path=/";
    }

    private scrollToBottom() {
        $("div.chatwell").scrollTop($("div.chatwell")[0].scrollHeight);
    }

    private getMessageTemplate(msg: MessageModel): string {
        return msg.SystemMessage ? "system-message-template" : "normal-message-template";
    }

    private getSystemMessage(text: string): string {
        if (text && text[0] == "@")
            return text.substring(text.indexOf(" "));
        return text;
    }
}

ko.applyBindings(new ChatViewModel());